// $Id: request.c 52 2011-04-20 17:06:01Z cedric.shih@gmail.com $
/*
 * Copyright (C) 2010 Cedric Shih <cedric.shih@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdlib.h>
#include <string.h>

#include <nio.h>
#include <nio/buffer.h>

#include "log.h"

#include "request.h"

struct niohttp_request *niohttp_request_new(void)
{
	struct niohttp_request *req;

	niohttp_debug("constructing");

	if (!(req = calloc(1, sizeof(struct niohttp_request)))) {
		niohttp_error("failed to calloc");
		goto error;
	}

	if (!(req->header = niohttp_headers_new())) {
		niohttp_error("failed to create header");
		goto error;
	}

	return req;

error:
	niohttp_request_free(req);
	return NULL;
}

void niohttp_request_free(struct niohttp_request *req)
{
	if (!req) {
		return;
	}

	niohttp_debug("destructing");

	if (req->method) {
		free(req->method);
	}

	if (req->uri) {
		free(req->uri);
	}

	if (req->version.string) {
		free(req->version.string);
	}

	if (req->protocol) {
		free(req->protocol);
	}

	if (req->header) {
		niohttp_headers_free(req->header);
	}

	free(req);
}

inline const char *niohttp_request_protocol(struct niohttp_request *req)
{
	return req->protocol;
}

inline int niohttp_request_set_protocol(struct niohttp_request *req,
		const char *protocol)
{
	return niohttp_request_nset_protocol(req, protocol, strlen(protocol));
}

int niohttp_request_nset_protocol(struct niohttp_request *req,
		const char *protocol, size_t len)
{
	int rc;
	char *str;

	if (!req || !protocol) {
		niohttp_error("null argument(s)");
		rc = NIOHTTP_EINVAL;
		goto finally;
	}

	niohttp_debug("setting protocol: %.*s", len, protocol);

	if (!(str = malloc(len + 1))) {
		niohttp_error("failed to malloc");
		rc = NIOHTTP_ENOMEM;
		goto finally;
	}

	if (req->protocol) {
		free(req->protocol);
	}

	req->protocol = str;
	memcpy(req->protocol, protocol, len);
	req->protocol[len] = '\0';
	rc = 0;

finally:
	return rc;
}

inline const char *niohttp_request_version(struct niohttp_request *req)
{
	return req->version.string;
}

inline int niohttp_request_major_version(struct niohttp_request *req)
{
	return req->version.major;
}

inline int niohttp_request_minor_version(struct niohttp_request *req)
{
	return req->version.minor;
}

inline int niohttp_request_set_version(struct niohttp_request *req,
		const char *version)
{
	return niohttp_request_nset_version(req, version, strlen(version));
}

inline int niohttp_request_nset_version(struct niohttp_request *req,
		const char *version, size_t len)
{
	return niohttp_version_nset(&req->version, version, len);
}

inline const char *niohttp_request_method(struct niohttp_request *req)
{
	return req->method;
}

inline int niohttp_request_set_method(struct niohttp_request *req,
		const char *method)
{
	return niohttp_request_nset_method(req, method, strlen(method));
}

int niohttp_request_nset_method(struct niohttp_request *req,
		const char *method, size_t len)
{
	int rc;
	char *str;

	if (!req || !method) {
		niohttp_error("null argument(s)");
		rc = NIOHTTP_EINVAL;
		goto finally;
	}

	niohttp_debug("setting method: %.*s", len, method);

	if (!(str = malloc(len + 1))) {
		niohttp_error("failed to malloc");
		rc = NIOHTTP_ENOMEM;
		goto finally;
	}

	if (req->method) {
		free(req->method);
	}

	req->method = str;
	memcpy(req->method, method, len);
	req->method[len] = '\0';
	rc = 0;

finally:
	return rc;
}

inline const char *niohttp_request_uri(struct niohttp_request *req)
{
	return req->uri;
}

inline int niohttp_request_set_uri(struct niohttp_request *req,
		const char *uri)
{
	return niohttp_request_nset_uri(req, uri, strlen(uri));
}

int niohttp_request_nset_uri(struct niohttp_request *req,
		const char *uri, size_t len)
{
	int rc;
	char *str;

	if (!req || !uri) {
		niohttp_error("null argument(s)");
		rc = NIOHTTP_EINVAL;
		goto finally;
	}

	niohttp_debug("setting URI: %.*s", len, uri);

	if (!(str = malloc(len + 1))) {
		niohttp_error("failed to malloc");
		rc = NIOHTTP_ENOMEM;
		goto finally;
	}

	if (req->uri) {
		free(req->uri);
	}

	req->uri = str;
	memcpy(req->uri, uri, len);
	req->uri[len] = '\0';
	rc = 0;

finally:
	return rc;
}

inline int niohttp_request_set_header(struct niohttp_request *req,
		const char *name, const char *value)
{
	if (!req) {
		return NIOHTTP_EINVAL;
	}

	return niohttp_headers_set(req->header, name, value);
}

inline const char *niohttp_request_header(struct niohttp_request *req,
		const char *name)
{
	if (!req || !name) {
		return NULL;
	}

	return niohttp_headers_get(req->header, name);
}

inline int niohttp_request_remove_header(struct niohttp_request *req,
		const char *name)
{
	if (!req) {
		return NIOHTTP_EINVAL;
	}

	return niohttp_headers_remove(req->header, name);
}

inline struct niohttp_header_list *niohttp_request_headers(
		struct niohttp_request *req)
{
	return &req->header->list;
}

inline int niohttp_request_clear_headers(struct niohttp_request *req)
{
	if (!req) {
		return NIOHTTP_EINVAL;
	}

	niohttp_headers_clear(req->header);
	return 0;
}

int niohttp_request_format(struct niohttp_request *req,
		struct niobuffer *buffer)
{
	int rc;
	struct niohttp_headers_entry *entry;

	if (!req->protocol) {
		niohttp_error("missing protocol");
		rc = NIOHTTP_EINVAL;
		goto finally;
	}

	if (!req->version.string) {
		niohttp_error("missing version");
		rc = NIOHTTP_EINVAL;
		goto finally;
	}

	if (!req->method) {
		niohttp_error("missing method");
		rc = NIOHTTP_EINVAL;
		goto finally;
	}

	if (!req->uri) {
		niohttp_error("missing URI");
		rc = NIOHTTP_EINVAL;
		goto finally;
	}

	niohttp_debug("formatting request to buffer");

	niohttp_debug("putting first line");

	if ((rc = niobuffer_putf(buffer, "%s %s %s/%s\r\n",
			req->method, req->uri, req->protocol,
			req->version.string))) {
		niohttp_error("failed to put first line: %s", nio_strerror(rc));
		rc = NIOHTTP_EBUFFER;
		goto finally;
	}

	niohttp_debug("putting headers");

	RB_FOREACH(entry, niohttp_headers_tree, &req->header->tree) {
		if ((rc = niobuffer_putf(buffer, "%s: %s\r\n",
				entry->name, entry->value))) {
			niohttp_error("failed to put header: %s",
					nio_strerror(rc));
			rc = NIOHTTP_EBUFFER;
			goto finally;
		}
	}

	niohttp_debug("putting end of header");

	if ((rc = niobuffer_putf(buffer, "%s", "\r\n"))) {
		niohttp_error("failed to put end of header: %s",
				nio_strerror(rc));
		rc = NIOHTTP_EBUFFER;
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}
